
#region Imported Namespaces
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Windows.Forms;

using BO = PHSRAG.BusinessObjects;
using Utility = PHSRAG.Utility;
using Policy = PHSRAG.Policy;
#endregion

namespace BusinessObjectsTest
{
	/// <summary>
	/// The BusinessObjectsTest is a simple test harness to test the BusinessObject assembly in the context
	/// of a parent/child object relation.
	/// </summary>
	public class BusinessObjectsTest : Form
	{
		#region Instance Variables
		private DataGrid		childGrid;
		private Container		components = null;
		private string			connectionString;
		private Button			loadBusinessObject;
		private Panel			panel;
		private ChildBO			childBO;
		private DataGrid		parentGrid;
		private Button			save;
		private Splitter		splitter;
		#endregion

		#region Parent Class
		/// <summary>
		/// The ParentBO is a direct descendent of the BusinessObject. Note that in this test, the ParentBO
		/// is not instantiated or accessed directly. It is completely controlled by its descendant class.
		/// </summary>
		private class ParentBO : BO.SaveableBusinessObject
		{
			#region Instance Variables
			private SqlCommand	addCommand;
			private DataGrid	dataGrid;
			private SqlCommand	deleteCommand;
			private SqlCommand	updateCommand;
			#endregion

			#region Constructor
			/// <summary>
			/// Initialize the state of this object and create commands to handle insert/delete/update.
			/// </summary>
			/// <param name="dataGrid">Reference to the DataGrid which displays the contents of the
			/// associated DataTable</param>
			/// <param name="cachePolicy">Interface to the cache policy in enforcement</param>
			/// <param name="securityPolicy">Interface to the security policy in enforcement</param>
			public ParentBO(Policy.ICachePolicy cachePolicy, Policy.ISecurityPolicy securityPolicy, DataGrid dataGrid)
				: base(cachePolicy, securityPolicy)
			{
				this.dataGrid = dataGrid;
				CreateCommands();
			}
			#endregion

			#region BusinessObject Methods
			public override void Cache()
			{
			}

			/// <summary>
			/// Load the "parent" table within the DataContainer with data by executing the stored procedure
			/// "GetAllParents". The loaded data is displayed in the associated DataGrid.
			/// </summary>
			/// <param name="connectionString">Connection string to the database</param>
			public override void Load(string connectionString, params object[] args)
			{
				Utility.DataAccess.LoadDataSet(DataContainer, "Parent", connectionString, "GetAllParents");
				dataGrid.DataSource = DataContainer.Tables["Parent"];
			}

			/// <summary>
			/// Throw an exception since this method is not to be used in this test. Save operations on this
			/// object is directly carried out using one or more of its protected Execute* methods. This is
			/// not the only way to implement the logic, but is just one simple way.
			/// </summary>
			/// <param name="connection">An instance of an open connection to the database</param>
			public override void Save(string connectionString)
			{
				throw new Exception("ParetBO Save must not be invoked directly");
			}

			public override void UnCache()
			{
			}

			#endregion

			#region Protected Methods
			/// <summary>
			/// This method is invoked by the derived object to carry out deletion of one or more rows from
			/// the associated DataTable. Nothing is done if there are no rows marked for deletion within the
			/// DataTable.
			/// </summary>
			/// <param name="connection">An instance of an open connection to the database</param>
			protected void ExecuteDelete(SqlConnection connection)
			{
				if (DataContainer.Tables["Parent"].Select("", "", DataViewRowState.Deleted).Length > 0)
				{
					SqlDataAdapter adapter = new SqlDataAdapter();
					(adapter.DeleteCommand = deleteCommand).Connection = connection;
					adapter.Update(DataContainer, "Parent");
				}
			}

			/// <summary>
			/// This method is invoked by the derived object to carry out insertion of one or more rows from
			/// the associated DataTable. Nothing is done if there are no rows marked for insertion within the
			/// DataTable.
			/// </summary>
			/// <param name="connection">An instance of an open connection to the database</param>
			protected void ExecuteInsert(SqlConnection connection)
			{
				if (DataContainer.Tables["Parent"].Select("", "", DataViewRowState.Added).Length > 0)
				{
					SqlDataAdapter adapter = new SqlDataAdapter();
					(adapter.InsertCommand = addCommand).Connection = connection;
					adapter.Update(DataContainer, "Parent");
				}
			}

			/// <summary>
			/// This method is invoked by the derived object to carry out update of one or more rows from
			/// the associated DataTable. Nothing is done if there are no rows marked for updated within the
			/// DataTable.
			/// </summary>
			/// <param name="connection">An instance of an open connection to the database</param>
			protected void ExecuteUpdate(SqlConnection connection)
			{
				if (DataContainer.Tables["Parent"].Select("", "", DataViewRowState.ModifiedCurrent).Length > 0)
				{
					SqlDataAdapter adapter = new SqlDataAdapter();
					(adapter.UpdateCommand = updateCommand).Connection = connection;
					adapter.Update(DataContainer, "Parent");
				}
			}
			#endregion

			#region Private Methods
			/// <summary>
			/// Create the set of SqlCommand objects used for the insertion, deletion, and updates made on
			/// the associated DataTable. Notice that each command is initialized to invoke a stored
			/// procedure implying that each command may have its own set of parameters in line with that
			/// stored procedure.
			/// </summary>
			private void CreateCommands()
			{
				addCommand = new SqlCommand();
				addCommand.CommandText = "AddParent";
				addCommand.CommandType = CommandType.StoredProcedure;
				addCommand.Parameters.Add("@firstName", SqlDbType.VarChar).SourceColumn = "FirstName";
				addCommand.Parameters.Add("@lastName", SqlDbType.VarChar).SourceColumn = "LastName";

				deleteCommand = new SqlCommand();
				deleteCommand.CommandText = "DeleteParent";
				deleteCommand.CommandType = CommandType.StoredProcedure;
				deleteCommand.Parameters.Add("@id", SqlDbType.Int).SourceColumn = "ID";

				updateCommand = new SqlCommand();
				updateCommand.CommandText = "UpdateParent";
				updateCommand.CommandType = CommandType.StoredProcedure;
				updateCommand.Parameters.Add("@id", SqlDbType.Int).SourceColumn = "ID";
				updateCommand.Parameters.Add("@firstName", SqlDbType.VarChar).SourceColumn = "FirstName";
				updateCommand.Parameters.Add("@lastName", SqlDbType.VarChar).SourceColumn = "LastName";
			}
			#endregion
		}
		#endregion

		#region Class ChildBO
		/// <summary>
		/// The ChildBO class is a direct descendant of the ParentBO class. This renders it to also be a
		/// BusinessObject. Even though the base class is not abstract (it could have been), all interactions
		/// with it are through this class.
		/// </summary>
		private class ChildBO : ParentBO
		{
			#region Instance Variables
			private SqlCommand	addCommand;
			private DataGrid	dataGrid;
			private SqlCommand	deleteCommand;
			private SqlCommand	updateCommand;
			#endregion

			#region Constructor
			/// <summary>
			/// Initialize the state of this object and create commands to handle insert/delete/update.
			/// Set the DataContainer to an empty DataSet. This class and its parent share that DataSet.
			/// </summary>
			/// <param name="parentGrid">Reference to the DataGrid which displays the contents of the
			/// DataTable associated with the ParentBO object</param>
			/// <param name="childGrid">Reference to the DataGrid which displays the contents of the
			/// DataTable associated with this object</param>
			/// <param name="cachePolicy">Interface to the security policy in enforcement</param>
			/// <param name="securityPolicy">Interface to the security policy in enforcement</param>
			public ChildBO(Policy.ICachePolicy cachePolicy, Policy.ISecurityPolicy securityPolicy,
				DataGrid parentGrid, DataGrid childGrid) : base(cachePolicy, securityPolicy, parentGrid)
			{
				dataGrid = childGrid;
				CreateCommands();
				DataContainer = new DataSet();
			}
			#endregion

			#region BusinessObject Methods
			/// <summary>
			/// Reset the DataContainer so as not to let history intervene. Ask the base class to load
			/// itself, and then load the "child" table within the DataContainer with data by executing
			/// the stored procedure "GetAllChildren". The loaded data is displayed in the DataGrids.
			/// </summary>
			/// <param name="connectionString">Connection string to the database</param>
			public override void Load(string connectionString, params object[] args)
			{
				DataContainer.Reset();

				base.Load(connectionString);
				Utility.DataAccess.LoadDataSet(DataContainer, "Child", connectionString, "GetAllChildren");
				dataGrid.DataSource = DataContainer.Tables["Child"];

				// Setup an explicit relation between the "parent" and "child" DataTable so as to avoid
				// adding incompatible data via the UI. This is done only to trap errors in the UI and not
				// at the database level.

				if (!DataContainer.Relations.Contains("ParentChild"))
					DataContainer.Relations.Add("ParentChild", DataContainer.Tables["Parent"].Columns["ID"],
						DataContainer.Tables["Child"].Columns["ParentID"]);
			}

			/// <summary>
			/// Saving this object actually involves three things: deleting all rows from the database
			/// based on rows marked for deletion in the DataTables, inserting new rows into the database
			/// based on newly added rows in the DataTables, and updating rows in the database based on
			/// rows that have been updated in the DataTables.
			/// When one or rows are added, deleted, changed in the DataGrids, the underlying DataTables
			/// automatically track those actions.
			/// </summary>
			/// <param name="connectionString">Connection string to the database</param>
			public override void Save(string connectionString)
			{
				SqlConnection connection = new SqlConnection(connectionString);
				try
				{
					connection.Open();
					ExecuteDelete(connection);
					base.ExecuteDelete(connection);

					base.ExecuteInsert(connection);
					ExecuteInsert(connection);

					base.ExecuteUpdate(connection);
					ExecuteUpdate(connection);
				}
				finally
				{
					if (connection.State != ConnectionState.Closed)
						connection.Close();
				}
			}
			#endregion

			#region Private Methods
			/// <summary>
			/// Create the set of SqlCommand objects used for the insertion, deletion, and updates made on
			/// the associated DataTable. Notice that each command is initialized to invoke a stored
			/// procedure implying that each command may have its own set of parameters in line with that
			/// stored procedure.
			/// </summary>
			private void CreateCommands()
			{
				addCommand = new SqlCommand();
				addCommand.CommandText = "AddChild";
				addCommand.CommandType = CommandType.StoredProcedure;
				addCommand.Parameters.Add("@parentID", SqlDbType.Int).SourceColumn = "ParentID";
				addCommand.Parameters.Add("@firstName", SqlDbType.VarChar).SourceColumn = "FirstName";
				addCommand.Parameters.Add("@lastName", SqlDbType.VarChar).SourceColumn = "LastName";

				deleteCommand = new SqlCommand();
				deleteCommand.CommandText = "DeleteChild";
				deleteCommand.CommandType = CommandType.StoredProcedure;
				deleteCommand.Parameters.Add("@id", SqlDbType.Int).SourceColumn = "ID";

				updateCommand = new SqlCommand();
				updateCommand.CommandText = "UpdateChild";
				updateCommand.CommandType = CommandType.StoredProcedure;
				updateCommand.Parameters.Add("@id", SqlDbType.Int).SourceColumn = "ID";
				updateCommand.Parameters.Add("@parentID", SqlDbType.Int).SourceColumn = "ParentID";
				updateCommand.Parameters.Add("@firstName", SqlDbType.VarChar).SourceColumn = "FirstName";
				updateCommand.Parameters.Add("@lastName", SqlDbType.VarChar).SourceColumn = "LastName";
			}

			/// <summary>
			/// This method is invoked by the derived object to carry out deletion of one or more rows from
			/// the associated DataTable. Nothing is done if there are no rows marked for deletion within the
			/// DataTable.
			/// </summary>
			/// <param name="connection">An instance of an open connection to the database</param>
			private new void ExecuteDelete(SqlConnection connection)
			{
				if (DataContainer.Tables["Child"].Select("", "", DataViewRowState.Deleted).Length > 0)
				{
					deleteCommand.Connection = connection;
				
					SqlDataAdapter adapter = new SqlDataAdapter();
					adapter.DeleteCommand = deleteCommand;
					adapter.Update(DataContainer, "Child");
				}
			}

			/// <summary>
			/// This method is invoked by the derived object to carry out insertion of one or more rows from
			/// the associated DataTable. Nothing is done if there are no rows marked for insertion within the
			/// DataTable.
			/// </summary>
			/// <param name="connection">An instance of an open connection to the database</param>
			private new void ExecuteInsert(SqlConnection connection)
			{
				if (DataContainer.Tables["Child"].Select("", "", DataViewRowState.Added).Length > 0)
				{
					addCommand.Connection = connection;
				
					SqlDataAdapter adapter = new SqlDataAdapter();
					adapter.InsertCommand = addCommand;
					adapter.Update(DataContainer, "Child");
				}
			}

			/// <summary>
			/// This method is invoked by the derived object to carry out update of one or more rows from
			/// the associated DataTable. Nothing is done if there are no rows marked for updated within the
			/// DataTable.
			/// </summary>
			/// <param name="connection">An instance of an open connection to the database</param>
			private new void ExecuteUpdate(SqlConnection connection)
			{
				if (DataContainer.Tables["Child"].Select("", "", DataViewRowState.ModifiedCurrent).Length > 0)
				{
					updateCommand.Connection = connection;

					SqlDataAdapter adapter = new SqlDataAdapter();
					adapter.UpdateCommand = updateCommand;
					adapter.Update(DataContainer, "Child");
				}
			}
			#endregion
		}
		#endregion

		#region Constructor
		/// <summary>
		/// Initialize the state of this object, create an instance of the ChildBO, and retrieve the
		/// database connection string from the application configuration file.
		/// </summary>
		public BusinessObjectsTest()
		{
			InitializeComponent();

			childBO = new ChildBO(new Policy.NullCachePolicy(), new Policy.NullSecurityPolicy(), parentGrid, childGrid);
			connectionString = Utility.Settings.GetKeyValue("ConnectionString", "");
		}
		#endregion

		#region Overrides
		protected override void Dispose(bool disposing)
		{
			if (disposing && (components != null))
				components.Dispose();
			base.Dispose(disposing);
		}
		#endregion

		#region Event Handlers
		private void OnLoadBusinessObject(object sender, System.EventArgs e)
		{
			LoadSave(true);
		}

		private void OnSaveBusinessObject(object sender, System.EventArgs e)
		{
			if (LoadSave(false))
				LoadSave(true);
		}
		#endregion

		#region Private Methods
		/// <summary>
		/// Load or save the BusinessObject hierarchy based on the requested action.
		/// </summary>
		/// <param name="load">Set to True for Loading, False for Saving</param>
		/// <returns></returns>
		private bool LoadSave(bool load)
		{
			bool result = false;
			try
			{
				if (load)
					childBO.Load(connectionString);
				else
					childBO.Save(connectionString);
				result = true;
			}
			catch (Exception ex)
			{
				System.Diagnostics.Trace.WriteLine(Utility.ExceptionReport.GetExceptionInfo(ex, false));
			}
			return result;
		}
		#endregion

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			this.panel = new System.Windows.Forms.Panel();
			this.save = new System.Windows.Forms.Button();
			this.loadBusinessObject = new System.Windows.Forms.Button();
			this.childGrid = new System.Windows.Forms.DataGrid();
			this.splitter = new System.Windows.Forms.Splitter();
			this.parentGrid = new System.Windows.Forms.DataGrid();
			this.panel.SuspendLayout();
			((System.ComponentModel.ISupportInitialize)(this.childGrid)).BeginInit();
			((System.ComponentModel.ISupportInitialize)(this.parentGrid)).BeginInit();
			this.SuspendLayout();
			// 
			// panel
			// 
			this.panel.Controls.Add(this.save);
			this.panel.Controls.Add(this.loadBusinessObject);
			this.panel.Dock = System.Windows.Forms.DockStyle.Top;
			this.panel.Location = new System.Drawing.Point(0, 0);
			this.panel.Name = "panel";
			this.panel.Size = new System.Drawing.Size(292, 40);
			this.panel.TabIndex = 0;
			// 
			// save
			// 
			this.save.Location = new System.Drawing.Point(96, 8);
			this.save.Name = "save";
			this.save.TabIndex = 2;
			this.save.Text = "Save";
			this.save.Click += new System.EventHandler(this.OnSaveBusinessObject);
			// 
			// loadBusinessObject
			// 
			this.loadBusinessObject.Location = new System.Drawing.Point(8, 8);
			this.loadBusinessObject.Name = "loadBusinessObject";
			this.loadBusinessObject.TabIndex = 0;
			this.loadBusinessObject.Text = "Load";
			this.loadBusinessObject.Click += new System.EventHandler(this.OnLoadBusinessObject);
			// 
			// childGrid
			// 
			this.childGrid.AllowSorting = false;
			this.childGrid.CaptionVisible = false;
			this.childGrid.DataMember = "";
			this.childGrid.Dock = System.Windows.Forms.DockStyle.Bottom;
			this.childGrid.HeaderForeColor = System.Drawing.SystemColors.ControlText;
			this.childGrid.Location = new System.Drawing.Point(0, 123);
			this.childGrid.Name = "childGrid";
			this.childGrid.Size = new System.Drawing.Size(292, 150);
			this.childGrid.TabIndex = 1;
			// 
			// splitter
			// 
			this.splitter.Cursor = System.Windows.Forms.Cursors.HSplit;
			this.splitter.Dock = System.Windows.Forms.DockStyle.Bottom;
			this.splitter.Location = new System.Drawing.Point(0, 120);
			this.splitter.Name = "splitter";
			this.splitter.Size = new System.Drawing.Size(292, 3);
			this.splitter.TabIndex = 2;
			this.splitter.TabStop = false;
			// 
			// parentGrid
			// 
			this.parentGrid.CaptionVisible = false;
			this.parentGrid.DataMember = "";
			this.parentGrid.Dock = System.Windows.Forms.DockStyle.Fill;
			this.parentGrid.HeaderForeColor = System.Drawing.SystemColors.ControlText;
			this.parentGrid.Location = new System.Drawing.Point(0, 40);
			this.parentGrid.Name = "parentGrid";
			this.parentGrid.Size = new System.Drawing.Size(292, 80);
			this.parentGrid.TabIndex = 3;
			// 
			// BusinessObjectsTest
			// 
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.ClientSize = new System.Drawing.Size(292, 273);
			this.Controls.Add(this.parentGrid);
			this.Controls.Add(this.splitter);
			this.Controls.Add(this.childGrid);
			this.Controls.Add(this.panel);
			this.Name = "BusinessObjectsTest";
			this.Text = "BusinessObjects Test";
			this.panel.ResumeLayout(false);
			((System.ComponentModel.ISupportInitialize)(this.childGrid)).EndInit();
			((System.ComponentModel.ISupportInitialize)(this.parentGrid)).EndInit();
			this.ResumeLayout(false);

		}
		#endregion

		#region Entry Point
		[STAThread]
		static void Main() 
		{
			Application.Run(new BusinessObjectsTest());
		}
		#endregion
	}
}
